#include "Common.h"
#include "OpenGLRenderer.h"
#include "OpenGLShader.h"
#include "OpenGLShaderProgram.h"
#include "Utils/Util.h"

using namespace RayFrame;
using namespace glm;

void OpenGLShaderProgram::internalCreate()
{
  assert( m_vs->IsOK() && m_ps->IsOK() );

  m_ID = glCreateProgram();
  glAttachShader( m_ID, ((OpenGLShader*)m_vs)->m_ID );
  glAttachShader( m_ID, ((OpenGLShader*)m_ps)->m_ID );

  glLinkProgram( m_ID );

  GLint linked;
  glGetProgramiv(m_ID, GL_LINK_STATUS, &linked);
  if (!linked)  // check if it's correctly linked
  {
    GLint blen = 0;
    GLsizei slen = 0;

    glGetProgramiv(m_ID, GL_INFO_LOG_LENGTH , &blen);
    OpenGLRenderer::InstanceRef().CheckError();

    if (blen > 1) // display why link failed
    {
      GLchar* compiler_log = new GLchar[blen];
      glGetInfoLogARB(m_ID, blen, &slen, compiler_log);
      m_log = STRING(compiler_log);
      ErrorMsg( m_log );

      delete [] compiler_log;
    }
    m_isOK = false;
  }
  else
    m_isOK = true;
}

void OpenGLShaderProgram::internalDestroy()
{
  glDeleteProgram(m_ID);

  m_isOK = false;
}

void OpenGLShaderProgram::Begin()
{
  glUseProgram(m_ID);
}

void OpenGLShaderProgram::End()
{
  glUseProgram((GLuint)NULL);
}

GLhandleARB OpenGLShaderProgram::GetHandle(STRING name)
{
  ShaderVarMapIter iter = m_handles.find(name);
  if(iter != m_handles.end() )
    return iter->second;

  GLhandleARB handle = glGetUniformLocationARB( m_ID, name.c_str() );
  g_renderer->CheckError();
  m_handles.insert( std::make_pair<STRING, GLhandleARB>( name, handle ) );
  return handle;
};

bool OpenGLShaderProgram::BindImage(STRING varName, Texture* tex,
                                    Shader::ShaderType type)
{
  GLhandleARB handle = GetHandle(varName);
  OpenGLTexture* texture = dynamic_cast<OpenGLTexture*>(tex);
  assert(texture);

  GLuint unitID = -1;

  // TODO: how to avoid looping?
  // check if this texture is bound already
  for(ShaderTextureMapIter iter = m_textures.begin();
    iter != m_textures.end();
    iter++)
  {
    if(iter->second == texture)
    {
      unitID = iter->first;
      break;
    }
  }

  if(signed(unitID) == -1)
  {   //Bind the texture for the 1st time, allocate a unit id for it
    for(int i=0; i<MAX_TEXTURE_COUNT; i++)
    {
      if(m_textures.find(i) == m_textures.end())
      {
        unitID = i;
        m_textures[unitID] = texture;
        break;
      }
    }
  }

  if(signed(unitID) == -1)
    return false; // texture count exceed the limitation of video card

  // texture binding
  glActiveTextureARB(GL_TEXTURE0_ARB + unitID);
  tex->Begin();
  glUniform1i(handle, unitID);

  return true;
}

void OpenGLShaderProgram::SetShaderParam(STRING name, const int val,
                                         Shader::ShaderType type)
{
  GLhandleARB handle = GetHandle(name);

  glUniform1iARB(handle, val);
};

void OpenGLShaderProgram::SetShaderParam(STRING name, const float val,
                                         Shader::ShaderType type)
{
  GLhandleARB handle = GetHandle(name);

  glUniform1fARB(handle, val);
};

void OpenGLShaderProgram::SetShaderParam(STRING name, vec2 val,
                                         Shader::ShaderType type)
{
  GLhandleARB handle = GetHandle(name);

  glUniform2fARB(handle, val.x, val.y);
};

void OpenGLShaderProgram::SetShaderParam(STRING name, glm::vec3 val,
                                         Shader::ShaderType type)
{
  GLhandleARB handle = GetHandle(name);

  glUniform3fARB(handle, val.x, val.y, val.z);
};

void OpenGLShaderProgram::SetShaderParam(STRING name, glm::vec4 val,
                                         Shader::ShaderType type)
{
  GLhandleARB handle = GetHandle(name);

  glUniform4fARB(handle, val.x, val.y, val.z, val.w);
};

void OpenGLShaderProgram::SetShaderParam(STRING name, glm::mat4 val,
                                         Shader::ShaderType type)
{
  GLhandleARB handle = GetHandle(name);

  glUniformMatrix4fvARB(handle, 1, GL_FALSE, value_ptr(val));
}

Shader* RayFrame::OpenGLShaderProgram::CreateVertexShader()
{
  Shader* s = new OpenGLShader();
  s->SetShaderType(Shader::VERTEX_SHADER);
  InsertShader(EMPTY_STRING, s);

  return s;
}

Shader* RayFrame::OpenGLShaderProgram::CreatePixelShader()
{
  Shader* s = new OpenGLShader();
  s->SetShaderType(Shader::PIXEL_SHADER);
  InsertShader(EMPTY_STRING, s);

  return s;
}
